package com.venky.swf.plugins.lucene.index;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.Version;
import com.venky.cache.Cache;
import com.venky.core.collections.IgnoreCaseMap;
import com.venky.core.collections.IgnoreCaseSet;
import com.venky.core.string.StringUtil;
import com.venky.core.util.ObjectUtil;
import com.venky.extension.Extension;
import com.venky.extension.Registry;
import com.venky.swf.db.Database;
import com.venky.swf.db.JdbcTypeHelper.TypeConverter;
import com.venky.swf.db.JdbcTypeHelper.TypeRef;
import com.venky.swf.db.model.Model;
import com.venky.swf.db.model.reflection.ModelReflector;
import com.venky.swf.db.table.Record;
import com.venky.swf.exceptions.MultiException;
import com.venky.swf.plugins.lucene.index.background.IndexManager;
import com.venky.swf.plugins.lucene.index.common.ResultCollector;
public class LuceneIndexer {
static {
Registry.instance().registerExtension("com.venky.swf.routing.Router.shutdown",new Extension(){
@Override
public void invoke(Object... context) {
dispose();
}
});
}
private static Cache<String, LuceneIndexer> indexerCache = new Cache<String, LuceneIndexer>() {
/**
*
*/
private static final long serialVersionUID = -1207573047245047343L;
@Override
protected LuceneIndexer getValue(String tableName) {
return new LuceneIndexer(tableName);
}
};
public static <M extends Model> LuceneIndexer instance (Class<? extends Model> modelClass){
return instance(ModelReflector.instance(modelClass));
}
public static <M extends Model> LuceneIndexer instance (ModelReflector<? extends Model> modelReflector){
return (LuceneIndexer)indexerCache.get(modelReflector.getTableName());
}
private IgnoreCaseSet indexedColumns = new IgnoreCaseSet();
private Map<String,Class<? extends Model>> indexedReferenceColumns = new IgnoreCaseMap<Class<? extends Model>>();
private final String tableName ;
public String getTableName() {
return tableName;
}
private LuceneIndexer(String tableName) {
if (ObjectUtil.isVoid(tableName)){
throw new NullPointerException("Table name cannot be null!");
}
try {
this.tableName = tableName;
for (Class<? extends Model> mClass: Database.getTable(tableName).getReflector().getModelClasses()){
ModelReflector<? extends Model> ref = ModelReflector.instance(mClass);
for (Method indexedFieldGetter: ref.getIndexedFieldGetters()){
String indexedColumnName = ref.getColumnDescriptor(ref.getFieldName(indexedFieldGetter)).getName();
indexedColumns.add(indexedColumnName);
if (ref.getReferredModelGetters().size() > 0){
Method referredModelGetter = ref.getReferredModelGetterFor(indexedFieldGetter) ;
if (referredModelGetter != null){
Class<? extends Model> referredModelClass = ref.getReferredModelClass(referredModelGetter);
indexedReferenceColumns.put(indexedColumnName, referredModelClass);
}
}
}
}
}catch(Exception e){
throw new RuntimeException(e);
}
}
public boolean hasIndexedFields(){
return !indexedColumns.isEmpty();
}
public Set<String> getIndexedColumns(){
return indexedColumns;
}
private Document getDocument(Record r) throws IOException {
if (!hasIndexedFields()){
return null;
}
Document doc = new Document();
boolean addedFields = false;
for (String columnName: indexedColumns){
ModelReflector<?> reflector = Database.getTable(tableName).getReflector();
String fieldName = reflector.getFieldName(columnName);
Object value = reflector.get(r, fieldName);
if (!ObjectUtil.isVoid(value) ){
TypeRef<?> ref = Database.getJdbcTypeHelper(reflector.getPool()).getTypeRef(reflector.getFieldGetter(fieldName).getReturnType());
TypeConverter<?> converter = ref.getTypeConverter();
if (!ref.isBLOB()){
addedFields = true;
if (Reader.class.isAssignableFrom(ref.getJavaClass())){
doc.add(new Field(fieldName,converter.toString(value),Field.Store.NO,Index.ANALYZED));
}else{
Class<? extends Model> referredModelClass = indexedReferenceColumns.get(columnName);
String sValue = converter.toString(value);
if (ref.isNumeric() && referredModelClass != null){
ModelReflector<?> referredModelReflector = ModelReflector.instance(referredModelClass);
Model referred = Database.getTable(referredModelClass).get(((Number)converter.valueOf(value)).intValue());
if (referred != null){
doc.add(new Field(fieldName.substring(0,fieldName.length()-"_ID".length()),
StringUtil.valueOf(referred.getRawRecord().get(referredModelReflector.getDescriptionField())),
Field.Store.YES,Field.Index.ANALYZED));
}
}
doc.add(new Field(fieldName,sValue, Field.Store.YES,Field.Index.ANALYZED));
}
}
}else {
addedFields = true;
if (indexedReferenceColumns.containsKey(fieldName)){
doc.add(new Field(fieldName.substring(0,fieldName.length()-"_ID".length()),
"NULL", Field.Store.YES,Field.Index.ANALYZED));
}
doc.add(new Field(fieldName,"NULL",Field.Store.YES,Field.Index.ANALYZED));
}
}
if (addedFields){
doc.add(new Field("ID",StringUtil.valueOf(r.getId()), Field.Store.YES,Field.Index.NOT_ANALYZED));
}else {
doc = null;
}
return doc;
}
@SuppressWarnings("unchecked")
public List<Document> getDocuments(String luceneOperation){
Cache<String,List<Document>> documentsByTable = (Cache<String,List<Document>>)Database.getInstance().getCurrentTransaction().getAttribute(luceneOperation);
if (documentsByTable == null){
documentsByTable = new Cache<String, List<Document>>() {
/**
*
*/
private static final long serialVersionUID = 3445427618501574899L;
@Override
protected List<Document> getValue(String k) {
return new ArrayList<Document>();
}
};
Database.getInstance().getCurrentTransaction().setAttribute(luceneOperation,documentsByTable);
}
return documentsByTable.get(tableName);
}
public void addDocument(Record r) throws IOException{
if (!hasIndexedFields()){
return;
}
Document doc = getDocument(r);
if (doc != null){
getDocuments("lucene.added").add(doc);
}
}
public void updateDocument(Record r) throws IOException{
if (!hasIndexedFields()){
return;
}
Document doc = getDocument(r);
if (doc != null){
getDocuments("lucene.updated").add(doc);
}
}
public void removeDocument(Record r) throws IOException{
if (!hasIndexedFields()){
return;
}
Document doc = getDocument(r);
if (doc != null){
getDocuments("lucene.removed").add(doc);
}
}
public List<Integer> findIds(Query q, int numHits){
final List<Integer> ids = new ArrayList<Integer>();
fire(q ,numHits,new ResultCollector() {
public void found(Document d) {
ids.add(Integer.valueOf(d.getFieldable("ID").stringValue()));
}
});
return ids;
}
public Query constructQuery(String queryString){
String descriptionField = Database.getTable(tableName).getReflector().getDescriptionField();
String defaultField = null;
if (indexedColumns.contains(descriptionField)){
defaultField = descriptionField;
}else if (!indexedColumns.isEmpty()) {
defaultField = indexedColumns.first();
if (defaultField.endsWith("_ID")){
defaultField = defaultField.substring(0,defaultField.length() - "_ID".length());
}
}else {
defaultField = "ID";
}
try {
return new QueryParser(Version.LUCENE_35,defaultField,new StandardAnalyzer(Version.LUCENE_35)).parse(queryString);
} catch (ParseException e) {
MultiException ex = new MultiException("Could not form lucene query for:\n" + queryString + "\n");
ex.add(e);
throw ex;
}
}
public void fire(Query q ,int numHits, ResultCollector callback) {
try {
if (!hasIndexedFields()){
return;
}
IndexManager.instance().fire(tableName, q, numHits, callback);
}catch (Exception ex){
throw new RuntimeException(ex);
}
}
public static void dispose() {
LuceneIndexer.indexerCache.clear();
}
}